package com.apidoc;

import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ResolvableType;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.alibaba.fastjson.JSON;
import com.apidoc.annotation.Api;
import com.apidoc.annotation.ApiAction;
import com.apidoc.annotation.ApiParam;
import com.apidoc.annotation.ApiReqParams;
import com.apidoc.annotation.ApiRespParams;
import com.apidoc.bean.ApiDoc;
import com.apidoc.bean.ApiDocAction;
import com.apidoc.bean.ApiDocInfo;
import com.apidoc.bean.ApiDocModel;
import com.apidoc.bean.ApiDocParam;
import com.apidoc.bean.ApiDocReqParams;
import com.apidoc.bean.ApiDocRespParams;
import com.apidoc.bean.ApiDocTableParam;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * @Description: 生成API文档工具类
 * @Author: admin
 * @CreateDate: 2018/1/4 15:04
 */
public class GeneratorWebApiDoc {

	private static final  Logger logger = LoggerFactory.getLogger(GeneratorWebApiDoc.class) ;
   
    //保存文档信息
    private ApiDoc apiDoc = new ApiDoc();
    
    //java数据类型的一个映射
    private static final Map<String, String> dataTypeMap = new HashMap<String, String>()  ;

    //缓存 所有的方法
    private static final Map<String, Method> methodMap = new HashMap<>();
    
    //缓存 所有的方法
    private static final Map<String, ApiDocAction> methodActionMap = new HashMap<>();
    
    //缓存枚举类型
    private static final Map<Class<?>, String> enumTypeMap = new HashMap<>() ;
    
    //类-方法缓存
    private static final Map<Class<?>, ApiDocModel> mappingMethods = new HashMap<>() ;
    

	private RequestMappingHandlerMapping requestMappingHandlerMapping ;
    

    /**
     * 设置文档基本信息
     *
     * @param apiDocInfo
     */
    public GeneratorWebApiDoc setInfo(ApiDocInfo apiDocInfo) {
        this.apiDoc.setInfo(apiDocInfo);
        return this;
    }

 
    public GeneratorWebApiDoc setInfo(ApiDocInfo apiDocInfo , RequestMappingHandlerMapping requestMappingHandlerMapping) {
        this.apiDoc.setInfo(apiDocInfo);
        this.requestMappingHandlerMapping = requestMappingHandlerMapping ;
        return this;
    }


    /**
     * 得到某个包下所有的注解为@Api的controller类
     * 把controller类信息转换成文档信息数据
     * <p>
     * 对应一个controller：模块信息，action信息
     *
     * @param packageName 包名称
     */
    public ApiDoc generator(String... packageName) {
        //扫描指定包路径下所有包含指定注解的类 //得到所有标准了文档的controller类
    	if(mappingMethods.isEmpty()) {
    		Map<RequestMappingInfo, HandlerMethod> mappings = requestMappingHandlerMapping.getHandlerMethods() ;
    		mappings.forEach(( info , method ) -> {
        		Class<?> clazz = method.getMethod().getDeclaringClass()  ;
        		boolean startFlag = Stream.of(packageName).anyMatch(pns -> { return clazz.getName().startsWith(pns) ; }) ;
        		if(startFlag) { 
        			Api api = clazz.getAnnotation(Api.class) ;
            		if(clazz.getAnnotation(Api.class) != null) { //标注Api
            			ApiDocModel model = mappingMethods.get(clazz) ;
                		if(model == null) { //添加
                			model = new ApiDocModel() ;
                			model.setName(api.name());
                			model.setOrder(api.order());
                    		model.setRootMapping(api.mapping());
                    		model.setList(new ArrayList<ApiDocAction>());
                    		mappingMethods.put(clazz , model );
                		}
                		ApiAction apiAction = method.getMethod().getAnnotation(ApiAction.class) ; //标注ApiAction
                		if(apiAction != null) {
                			ApiDocAction action = new ApiDocAction() ;
                			String methodUUID = method.getMethod().getDeclaringClass() + "_" + method.getMethod().getName();
                			action.setMethoduuid(methodUUID);
                    		action.setMapping(CollectionUtil.join(info.getPatternsCondition().getPatterns(), " | ") ) ;
                    		String methods = "" ;
                    		Set<RequestMethod> ams =  info.getMethodsCondition().getMethods() ;
                    		if(CollectionUtil.isEmpty(ams)) {
                    			methods = apiAction.method().name().toLowerCase() ;
                    		}else {
                        		methods = CollectionUtil.join(ams , " | ").toLowerCase() ;
                    		}
                    		action.setMethod(methods);
                    		action.setName(apiAction.name());
                    		action.setDescription(apiAction.description());
                    		action.setOrder(apiAction.order());
                    		mappingMethods.get(clazz).getList().add(action) ;
                    		methodMap.put(methodUUID, method.getMethod());//加入缓存
                    		methodActionMap.put(methodUUID, action) ;
                		}
            		}
        		}
        		
        		
        	}); 
    	}
    	
    	logger.info(JSON.toJSONString(mappingMethods));
    	mappingMethods.forEach( (k , v)-> {
    		v.getList().sort(Comparator.comparing(ApiDocAction::getName));
    		v.getList().sort(Comparator.comparing(ApiDocAction::getOrder));
    	});
    	List<ApiDocModel> list = mappingMethods.entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toList()) ;
    	list.sort(Comparator.comparing(ApiDocModel::getName));
    	list.sort(Comparator.comparing(ApiDocModel::getOrder));
    	apiDoc.setModels(list);
    	return apiDoc ;
    }

   

    /**
     * 根据方法名唯一标识 获取 该方法的文档信息
     */
    public ApiDocAction getApiOfMethod(String methodUUID) {
    	ApiDocAction methodAction = methodActionMap.get(methodUUID);
        if (null == methodAction) {
            throw new RuntimeException("找不到相应方法： " + methodMap.get(methodUUID).getName());
        }
        ApiDocAction apiDocAction = new ApiDocAction();
        //设置基本信息
        apiDocAction.setName(methodAction.getName());
        apiDocAction.setMapping(methodAction.getMapping());
        apiDocAction.setDescription(methodAction.getDescription());
        apiDocAction.setMethod(methodAction.getMethod());
        
        Method method = methodMap.get(methodUUID) ;
        //设置请求参数
        apiDocAction.setReqParams(getApiDocReqParams(method));
        //设置响应参数
        apiDocAction.setRespParams(getApiDocRespParams(method));
        return apiDocAction;
       
    }

    /**
     * 获得action的请求参数
     *
     * @param actionMethod
     * @return
     */
    private ApiDocReqParams getApiDocReqParams(Method actionMethod) {
        ApiReqParams apiReqParams = actionMethod.getAnnotation(ApiReqParams.class);
        ApiDocReqParams apiDocReqParams = null;
        if (null != apiReqParams) {
            apiDocReqParams = new ApiDocReqParams();
            //设置请求参数的类型
            apiDocReqParams.setType(apiReqParams.type().toString());
            //设置请求参数列表
            apiDocReqParams.setParams(getApiParam(apiReqParams.value()));
            //设置描述
            apiDocReqParams.setDescription(apiReqParams.description());
        }
        return apiDocReqParams;
    }

    /**
     * 获得action的响应参数
     *
     * @param actionMethod
     * @return
     */
    private ApiDocRespParams getApiDocRespParams(Method actionMethod) {
        ApiRespParams apiRespParams = actionMethod.getAnnotation(ApiRespParams.class);
        ApiDocRespParams apiDocRespParams = null;
        if (null != apiRespParams) {
            apiDocRespParams = new ApiDocRespParams();
            List<ApiDocParam> apiDocRespParamList = getApiParam(apiRespParams.value());
            //设置描述
            apiDocRespParams.setDescription(apiRespParams.description());
            apiDocRespParams.setParams(apiDocRespParamList);
        }
        return apiDocRespParams;
    }

    //获取api参数
    private List<ApiDocParam> getApiParam(ApiParam[] apiParamsArray) {
        List<ApiDocParam> apiDocParams = null;
        if (ArrayUtil.isNotEmpty(apiParamsArray)) {
            apiDocParams = new ArrayList<>();
            for (ApiParam apiParam : apiParamsArray) {
                if (null != apiParam) {
                    Class<?> paramClass = apiParam.value();
                    if (paramClass != void.class && paramClass != Void.class) {//如果有class类型则反射类，获取参数的信息
                    	if(StrUtil.isNotBlank(apiParam.name())) { //名字不为空 class对应的父节点为当前name
                    		keepParam(apiDocParams, apiParam) ;
                    		keepParam4Sub(apiDocParams, apiParam, paramClass , //如果没有标注object 就使用name这就要保证name唯一 不然树形结构关联会有错误
                    				StrUtil.isBlank(apiParam.object()) ? apiParam.name() : apiParam.object() ) ; //
                		}else { //名字为空 class对应的父节点为该belongTo指定
                			keepParam4Sub(apiDocParams, apiParam, paramClass , 
                					StrUtil.isNotBlank(apiParam.belongTo()) ? apiParam.belongTo() : apiParam.object()  );
                		}
                    } else {
                        keepParam(apiDocParams, apiParam);
                    }
                }
            }
        }
        List<ApiDocParam> list = list2Json(apiDocParams);

        System.err.println(JSON.toJSONString(getApiDocTableParamList(list, "0"))) ;
        return list ;
    }

    /**
     * 解析Class里面的字段 目前只支持基本数据类型
     * @param apiDocParams
     * @param apiParam
     * @param paramClass
     */
    private void keepParam4Sub(List<ApiDocParam> apiDocParams, ApiParam apiParam, Class<?> paramClass , String belongTo ) {
        //解析类的属性，每一个属性组成一个参数对象
    	List<ApiDocParam> classDetailList = getApiDocModelList(apiParam, paramClass , belongTo ) ;
        if(CollectionUtil.isNotEmpty(classDetailList)) {
        	apiDocParams.addAll(classDetailList) ;
        }
    }
    
    

    

    /**
     * 保存该参数
     *
     * @param apiDocParams
     * @param apiParam
     */
    private void keepParam(List<ApiDocParam> apiDocParams, ApiParam apiParam) {
        ApiDocParam apiDocParam = new ApiDocParam();

        apiDocParam.setName(apiParam.name());
        apiDocParam.setDataType(apiParam.dataType().toString());
        apiDocParam.setDescription(apiParam.description());
        apiDocParam.setDefaultValue(apiParam.defaultValue());
        apiDocParam.setRequired(apiParam.required());
        apiDocParam.setObjectName(apiParam.object());
        apiDocParam.setBelongTo(apiParam.belongTo());
        apiDocParam.setRemark(apiParam.remark());

        enumTypeApiParam(apiParam , apiDocParam ) ;
        apiDocParams.add(apiDocParam);
    }

    
    private void enumTypeApiParam(ApiParam apiParam , ApiDocParam apiDocParam ) {
    	Class<?> enumType = apiParam.enumType() ;
        try {
            if(Enum.class.isAssignableFrom(enumType)) {
            	if(enumTypeMap.get(enumType) == null) {
            		Object[] objs = enumType.getEnumConstants() ;
                	if(ArrayUtil.isNotEmpty(objs)) {
                		Method md = enumType.getMethod("enumApiDoc") ;
                		StringBuilder sf = new StringBuilder() ;
                		for(Object object : objs) {
                    		if(md != null ) {
                        		Object result = md.invoke(object) ;
                        		sf.append(String.valueOf(result)) ;
                    		}
                		}
                		enumTypeMap.put(enumType, sf.toString()) ;
                	}
            	}
            	apiDocParam.setEnumTypes(enumTypeMap.get(enumType));
            }
        }catch (Exception e) {
        	logger.error("读取枚举字典类型 {} 数据 异常" , enumType.getName() );
        	logger.error(e.getMessage() , e) ;
		}
        if(StrUtil.isNotBlank(apiDocParam.getEnumTypes()) && 
        		StrUtil.isBlank(apiDocParam.getDefaultValue())) {
        	apiDocParam.setDefaultValue(apiDocParam.getEnumTypes());
        }
    }
    
    private List<ApiDocParam> getApiDocModelList(ApiParam apiParam, Class<?> paramClass , String belongTo ) {
    	
    	final List<ApiDocParam> list = new ArrayList<ApiDocParam>() ;
        Field[] fields = ReflectUtil.getFields(paramClass);
        List<ApiDocParam> nowList = Arrays.stream(fields)
                //去掉序列化字段serialVersionUID
                .filter( m -> m.getAnnotation(ApiParam.class) != null && !ArrayUtil.contains(apiParam.exclude() , m.getName()) ) 
                 //遍历
                .map(field -> {
                	ApiDocParam params = new ApiDocParam() ;
                	ApiParam fileApiParams = field.getAnnotation(ApiParam.class) ;
                	if(fileApiParams != null) {
                		params.setName(StrUtil.isBlank(fileApiParams.name()) ? field.getName() : fileApiParams.name()) ;
                		params.setDescription(fileApiParams.description());
                		params.setDefaultValue(fileApiParams.defaultValue());
                		params.setRequired(fileApiParams.required());
                		params.setBelongTo(belongTo) ; //上一级object
                		String type = getDataType(field.getType().getName()) ;
                		params.setDataType(type);
                		String object = StrUtil.isNotBlank(fileApiParams.object()) ? fileApiParams.object() : paramClass.getSimpleName() + "::" +  field.getName() ; 
                		params.setObjectName(object );
                		params.setRemark(fileApiParams.remark());
                		enumTypeApiParam(fileApiParams , params ) ;
                		
                		if(type.equalsIgnoreCase("object") || type.equalsIgnoreCase("array")
                				|| type.equalsIgnoreCase("Map")  || type.equalsIgnoreCase("HashMap") 
                			   ||  type.equalsIgnoreCase("JSON") || type.equalsIgnoreCase("JSONObject")
                			   || type.equalsIgnoreCase("JSONOArray")  ) { //特殊类型
                			
                			Type genericType = field.getGenericType() ;
                			if(List.class.isAssignableFrom(field.getType())) {
                				ResolvableType resolvableType = ResolvableType.forField(field);
                				Class<?> genricClass = resolvableType.getGeneric(0).resolve();
                			    if(genricClass != null) {
                    				list.addAll(getApiDocModelList( fileApiParams , genricClass , object  )) ;
                			    }
                			    
                			}else {
                				list.addAll(getApiDocModelList( fileApiParams , (Class<?>) genericType , object  )) ;
                			}
                		}
                		
                		return params ; 
                	}
                	return params ; 
         }).collect(Collectors.toList()) ;
        
        list.addAll(nowList) ;
        return list ;
    }
    
   
    
  
    
    /**
     * 获取参数的类型，把java的数据类型转换为前端要的数据类型 具体类型参考枚举类com.qdzklt.apidoc.enumeration.DateType
     *
     * @param s
     * @return
     */
    private String getDataType(String s) {
        if (dataTypeMap.size() == 0) {
            dataTypeMap.put(byte.class.getName(), "number");
            dataTypeMap.put(short.class.getName(), "number");
            dataTypeMap.put(int.class.getName(), "number");
            dataTypeMap.put(long.class.getName(), "number");
            dataTypeMap.put(float.class.getName(), "number");
            dataTypeMap.put(double.class.getName(), "number");
            dataTypeMap.put(Byte.class.getName(), "number");
            dataTypeMap.put(Short.class.getName(), "number");
            dataTypeMap.put(Integer.class.getName(), "number");
            dataTypeMap.put(Long.class.getName(), "number");
            dataTypeMap.put(Float.class.getName(), "number");
            dataTypeMap.put(Double.class.getName(), "number");
            dataTypeMap.put(BigDecimal.class.getName(), "number");
            dataTypeMap.put(BigInteger.class.getName(), "number");

            dataTypeMap.put(char.class.getName(), "string");
            dataTypeMap.put(Character.class.getName(), "string");
            dataTypeMap.put(String.class.getName(), "string");

            dataTypeMap.put(boolean.class.getName(), "boolean");
            dataTypeMap.put(Boolean.class.getName(), "boolean");

            dataTypeMap.put(Date.class.getName(), "date");
            dataTypeMap.put(Timestamp.class.getName(), "date");
            
            dataTypeMap.put(Map.class.getName(), "object");
            dataTypeMap.put(HashMap.class.getName(), "object");
            
            dataTypeMap.put(List.class.getName(), "array");
            dataTypeMap.put(Array.class.getName(), "array");
        }
        s = s.replace("class", "").trim();
        return StrUtil.isNotBlank(dataTypeMap.get(s)) ? dataTypeMap.get(s) : "object";
    }

    /**
     * 将list数据转换为tree结构数据
     *
     * @param apiDocParams
     * @return
     */
    private List<ApiDocParam> list2Json(List<ApiDocParam> apiDocParams) {
        if (CollectionUtil.isEmpty(apiDocParams)) {
            return null;
        }
        List<ApiDocParam> trees = new ArrayList<>();
        for (ApiDocParam treeNode : apiDocParams) {
            if ("0".equals(treeNode.getBelongTo())) {
                trees.add(treeNode);
            }
            for (ApiDocParam it : apiDocParams) {
                if (it.getBelongTo().equals(treeNode.getObjectName())) {
                    if (treeNode.getList() == null) {
                        treeNode.setList(new ArrayList<>());
                    }
                    boolean has = treeNode.getList().stream().anyMatch(his -> it.getName().equals(his.getName())) ;
                    if(!has) {
                        treeNode.getList().add(it) ;
                    }
                   
                }
            }
        }
        return trees;
    }
    
    List<ApiDocTableParam>  getApiDocTableParamList(List<ApiDocParam> list , String pid ){
    	
    	List<ApiDocTableParam> result = new ArrayList<ApiDocTableParam>() ;
    	if(CollectionUtil.isNotEmpty(list)) {
    		for(int i = 0 ; i < list.size() ; i ++) {
    			ApiDocParam apiDocParam  = list.get(i) ;
    			ApiDocTableParam dto = new ApiDocTableParam() ;
    			dto.setChildren(apiDocParam.getList() == null ? 0 : apiDocParam.getList().size());
    			dto.setPath(pid + "-" + i);
    			dto.setBelongTo(apiDocParam.getBelongTo());
    			dto.setDataType(apiDocParam.getDataType());
    			dto.setDefaultValue(apiDocParam.getDefaultValue());
    			dto.setDescription(apiDocParam.getDescription());
    			dto.setEnumTypes(apiDocParam.getEnumTypes());
    			dto.setName(apiDocParam.getName());
    			dto.setObjectName(apiDocParam.getObjectName());
    			dto.setRemark(apiDocParam.getRemark());
    			dto.setRequired(apiDocParam.isRequired());
    			result.add(dto) ;
    			if(CollectionUtil.isNotEmpty(apiDocParam.getList())) {
    				result.addAll(getApiDocTableParamList(apiDocParam.getList() , pid + "-" + i )) ;
    			}
    		}
    	}
    	return result ;
    	
    }
    

    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
		return requestMappingHandlerMapping;
	}

	public void setRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping) {
		this.requestMappingHandlerMapping = requestMappingHandlerMapping;
	}

    
}
